/*  sssax.cpp
 *  Created by Garrett williams on 5/19/05.
 *  Copyright 2005 Drag'N Flies. All rights reserved.
 */

#include "sssax.h"
#include <string.h>

SSAttribute::SSAttribute():
    name(0),
    value(0),
    next(0)
{
}

SSAttribute::SSAttribute(const char *n, const char *v) :
    name(0),
    value(0),
    next(0)
{
    set(n,v);
}

SSAttribute::~SSAttribute()
{
    if(name)  delete[] name;
    if(value) delete[] value;
    if(next)  delete next;
}

void 
SSAttribute::set(const char *n, const char *v)
{
    if(name)  delete[] name;
    if(value) delete[] value;
    
    name  = new char[strlen(n)];
    strcpy(name,n);
    name[strlen(n)+1] = 0;
    value = new char[strlen(v)];
    strcpy(value,v);
    value[strlen(v)+1] = 0;
}

void 
SSAttribute::add(const char *n, const char *v)
{
    if(name)  
    {
        next = new SSAttribute(n,v);
    }
    else
    {
        set(n,v);
    }
}

const char* SSAttribute::getTag()
{
    return name;
}

const char* SSAttribute::getValue()
{
    return value;
}

// thank your for the memory it is now MINE!
void SSAttribute::setNext(SSAttribute *n)
{
    next = n;
}

SSAttribute* SSAttribute::getNext()
{
    return next;
}

SSAttribute* SSAttribute::find(const char* a)
{
    SSAttribute *n = this;
    while(n)
    {
        if(0 == strcmp(n->name,a))
        {
            break;
        }
        n = n->next;
    }
    return n;
}


SSSAX::SSSAX() :
    attributes(0),
    parse_count(0),
    new_file(false)
{
    init();
}

SSSAX::SSSAX(const char* parse_file) :
    attributes(0),
    parse_count(0),
    new_file(false)
{
    init();
    setFile(parse_file);
}

SSSAX::~SSSAX()
{
    if(xml_in.is_open())
    {
        //xml_in.flush();
        xml_in.close();
    }
    if(file_name) delete[] file_name;
    if(tag_name)  delete[] tag_name;
    if(data_char) delete[] data_char;
    if(attributes)
    {
        //for(short i=0;attributes[i];i++) delete[] attributes[i];
        delete attributes;
    }
}

void SSSAX::init()
{
    file_name    = 0;
    attributes   = 0;
    attr_size    = 5;
    attr_count   = 0;
    data_char    = 0;
    data_size    = 128;
    data_count   = 0;
    user_data    = 0;
    tag_name     = 0;
    tag_length   = 0;
    line_count   = 0;
    parse_line   = 0;
    parse_size   = 30;

    //attributes  = new SSAttribute *[attr_size+1];
    //attributes[0] = 0;
    data_char   = new char[data_size+1];
    parse_line  = new char[parse_size];
}

void SSSAX::setFile(const char *parse_file,bool oc)
{
    if(parse_file)
    {
        file_name = new char[strlen(parse_file)+1];
        strcpy(file_name,parse_file);
    }
}

void SSSAX::readyWrite()
{
    xml_out.open(file_name);//,std::ios::out);
    if (!xml_out.is_open())
    {
        throw "SSSAX::readyWrite Unable to open file\n";
    }
}

void SSSAX::doParse()
{    
    xml_in.open(file_name,std::ios::in);
    if (xml_in.fail())
    {
        eof = true;
        new_file = true;
        //throw "SSSAX::doParse Unable to open file\n";
    }
    else
    {
        eof = false;
    }
   // try
    {
        parse();
    }
   // catch (const char* err)
    {
    //    throw err;
    }
    xml_in.close();
}

void SSSAX::addAttribute(const char *name, const char *value)
{
    SSAttribute *hand;
    hand = new SSAttribute(name,value);
    if(attributes)
    {
        attr_current->setNext(hand);
        attr_current = hand;
    }
    else
    {
        attr_current = attributes = hand;
    }
}

void SSSAX::clearAttr()
{
    if(attributes)
    {
        // clear out old attributes
        delete attributes;
        attributes = 0;
    }
}

void SSSAX::addChar(char **data, unsigned &size, char c)
{
    // needed extra for null.
    unsigned count = strlen(*data)+1;
    if(size <= count)
    {
        char *hand = new char[size*2+1];
        memcpy(hand,*data,size);
        delete[] *data;
        *data = hand;
        size = size*2;
    }
    (*data)[count-1] = c;
    (*data)[count] = 0;
}

// jump over white space. to nixt '<' or '>'.
SSJUMP SSSAX::jumpSpace()
{
    SSJUMP rt = SSJ_OTHER;
    char mark = 0;
    // jump over the following charters.
    // the char at parse_count should be the start of a tag or data.
    while(  (' '  == parse_line[parse_count]) || 
            ('<'  == parse_line[parse_count]) || 
            ('>'  == parse_line[parse_count]) || 
            ('\0' == parse_line[parse_count]) ||  // need this to catch the end of parse_line
            ('\n' == parse_line[parse_count]) || 
            ('\t' == parse_line[parse_count]) || 
            ('\r' == parse_line[parse_count]) )
    {
        if(0 == parse_line[parse_count])
        {
            if(!getLine())
            {
                throw "SSSAX::jumpSpace 1 unexpected end of file\n";
            }
        }
        else
        {
            if(SSJ_OTHER == rt)
            {
                // what is our location.
                switch(parse_line[parse_count])
                {
                    case '<':
                    {
                        mark = '<'; // keep track where this was found.
                        unsigned offset = 1;
                        // NOTE: There is a bug here the mark is lost. if at the end 
                        // of a read line.
                        // make shure there is somthing to look ahead at.
                        if(0 == parse_line[parse_count+1])
                        {
                            if(!getLine(true))
                            {
                                throw "SSSAX::jumpSpace 2 unexpected end of file\n";
                            }
                            else
                                offset = 1;
                        }
                        // look ahead to see what type of tag.
                        if('/' == parse_line[parse_count+offset])// found an end tag
                        {
                            return SSJ_END;
                        }
                        else
                        {
                            return SSJ_START;
                        }
                    }
                        break;
                    case '>':
                        //parse_count++; // jump the '>'
                        rt = SSJ_CLOSE;
                        mark = '>'; // keep track where this was found.
                        break;
                    case ' ':
                    case '\n':
                    case '\r':
                    case '\t':
                        rt = SSJ_SPACE;
                        break;
                }
            }
            // found tags close together.
            // found white space between tags. Ignor it.
            else if (('>' == mark) && ('<' == parse_line[parse_count]))
            {
                // make shure there is somthing to look ahead at.
                unsigned offset = 1;
                if(0 == parse_line[parse_count+offset])
                {
                    if(!getLine(true))
                    {
                        throw "SSSAX::jumpSpace 3 unexpected end of file\n";
                    }
                    else
                        offset = 1;
                }
                // look ahead to see what type of tag.
                if('/' == parse_line[parse_count+offset])// found an end tag
                {
                    rt = SSJ_END;
                }
                else
                {
                    rt = SSJ_START;
                }
                break;
            }
            // found empty tag error.
            else if (('<' == mark) && ('>' == parse_line[parse_count])) 
            {
                rt = SSJ_ERROR;
                break;
            }
            parse_count++;
        }
    }
    return rt;
}

// follow the tree of xml file.
void SSSAX::parse()
{
    SSJUMP begin_end;
    unsigned tn_size,data_size,en_size,an_size,av_size;
    tn_size = data_size = en_size = an_size = av_size = 16; // default word size.
    // create arrays for data from file.
    char *tag_name  = new char[tn_size];
    char *data      = new char[data_size];
    char *end_name  = new char[en_size];
    char *att_name  = new char[an_size];
    char *att_value = new char[av_size];
    tag_name[0]  = 0;
    data[0]      = 0;
    end_name[0]  = 0;
    att_name[0]  = 0;
    att_value[0] = 0;
    if(!getLine())
    {
        // must be a new file just return.
        return;
        //throw "SSSAX::parse parse unexpected end of file\n";
    }
    while(!eof)
    {
        try{
            begin_end = jumpSpace();
        }catch(...){
            break; // eof or error just quit here.
        } 
        if(SSJ_START == begin_end)
        {
            // found start of tag
            parseTag(&tag_name,tn_size);
            while(SSJ_SPACE == jumpSpace()) // found > 
            {
                // found attribute of tag
                parseAttName(&att_name,an_size);
                jumpSpace();
                parseAttValue(&att_value,av_size);
                // store attribute data.
                addAttribute(att_name,att_value);
                att_name[0] = 0;
                att_value[0] = 0;
            }
            // call callback for tag.
            if(tag_name[0])
            {
                startTagCB(user_data,tag_name,attributes);
                tag_name[0] = 0; // clear tag
                clearAttr();     // clear attributes.
            }
            // found data
            parseData(&data,data_size);
            // call callback for data
            if(data[0])
            {
                charDataCB(user_data,data);
                data[0] = 0; // clear data.
            }
        }
        else if(SSJ_END == begin_end)
        {
            parseTag(&end_name,en_size);
            // call callback for end of tag.
            if(end_name[0])
            {
                endTagCB(user_data,end_name);
                end_name[0] = 0; // clear end tag name.
            }
        }
        else
        {
            // should not be here.
            throw "SSSAX::parse Parse Error badly formed file\n";
        }
    }
}

// reset count in array zero out memory and load in next chunk of data.
bool SSSAX::getLine(unsigned keep)
{
    bool rt = true;
    if (!eof)
    {
        unsigned i,off=0;
        if('<' == parse_line[parse_count]) 
        {
            //memset(parse_line,0,parse_size);
            off = 1;
            parse_line[0] = '<'; // keep the start of the tag.
            //read_count = xml_in.read(&parse_line[1],parse_size-1); // keep the start of the tag.
            parse_count = 0;
        }
        //memset(parse_line,0,parse_size);
        parse_count = 0;
        for(i=off;i<parse_size-off-1;i++)
        {
            if(!xml_in.get(parse_line[i]))
            {
                break;
            }
        }
        //read_count = xml_in.read(parse_line,parse_size);
        parse_line[i] = 0;
        // return false if end of file or file error.
        if(xml_in.eof()) eof = true;
        if(xml_in.fail()) // returns true if unable to read full line of text.?
        {
            eof = true;
            //rt = false; 
        }
    }
    else rt = false;
    return rt;
}

unsigned SSSAX::parseTag(char **data, unsigned &data_size)
{
    while( !(('>' == parse_line[parse_count]) || (' ' == parse_line[parse_count])) )
    {
        if(0 == parse_line[parse_count])
        {
            if(!getLine())
            {
                throw "SSSAX::parseTag Unexpected end of file\n";
            }
        }
        else
        {
            // skip the unneeded chars.
            if( !(('/' == parse_line[parse_count]) ||
                  ('<' == parse_line[parse_count]) ))
                addChar(data,data_size,parse_line[parse_count]);
            parse_count++;
        }
    }
    return parse_count;
}

unsigned SSSAX::parseAttName(char **data, unsigned &data_size)
{
    while('=' != parse_line[parse_count])
    {
        if(0 == parse_line[parse_count])
        {
            if(!getLine())
            {
                throw "SSSAX::parseAttName Unexpected end of file\n";
            }
        }
        else if( ('<' == parse_line[parse_count]) || ('>' == parse_line[parse_count]) )
        {
                throw "SSSAX::parseAttName Bad XML file\n";
        }
        else
        {
            // spaces do not belong in attribute names.
            if(' ' != parse_line[parse_count]) 
            {
                addChar(data,data_size,parse_line[parse_count]);
            }
            parse_count++;
        }
    }
    parse_count++; // jump over the = 
    return parse_count;
}

// the size of data may change allow for this.
unsigned SSSAX::parseAttValue(char **data, unsigned &data_size)
{
    parse_count++; // attributes begin with a Quote
    if(0 == parse_line[parse_count])
    {
        if(!getLine())
        {
            throw "SSSAX::parseAttValue unexpected end of file\n";
        }
    }
    // read until a Quote or end of tag ends attribute data.
    while( !(('"' == parse_line[parse_count])) )//('>' == parse_line[parse_count]) || 
    {
        if(0 == parse_line[parse_count])
        {
            if(!getLine())
            {
                throw "SSSAX::parseAttValue unexpected end of file\n";
            }
        }
        else if('<' == parse_line[parse_count])
        {
                throw "SSSAX::parseAttValue Bad XML file\n";
        }
        else
        {
            // quotes do not belong in attribute data.
            if('\"' != parse_line[parse_count]) 
            {
                addChar(data,data_size,parse_line[parse_count]);
            }
            parse_count++;
        }
    }
    parse_count++; // jump over attribute ending Quote.
    return parse_count;
}

// return current location in parse_line array.
unsigned 
SSSAX::parseData(char **data, unsigned &data_size)
{
    while('<' != parse_line[parse_count])
    {
        if(0 == parse_line[parse_count])
        {
            if(!getLine())
            {
                throw "SSSAX::parseData end of file\n";
            }
        }
        else if('>' == parse_line[parse_count])
        {
                throw "SSSAX::parseData Bad XML file\n";
        }
        else
        {
            addChar(data,data_size,parse_line[parse_count]);
            parse_count++;
        }
    }
    return parse_count;
}

// indent: space count
// tag   : tag and attributes.
void    
SSSAX::writeStartTag(unsigned indent, const char* tag)
{
    if(xml_out.fail())// put a better check here!
        throw "SSSAX::writeStartTag NO file opened\n";
    for(unsigned i=0;i<indent;i++)
        xml_out << "\t";
    // write tag
    xml_out << "<" << tag;
}
void       
SSSAX::writeAttTag(const char* attribute)
{
    if(xml_out.fail())// put a better check here!
        throw "SSSAX::writeAttTag NO file opened\n";
    xml_out << " " << attribute << " = " ;
}

void       
SSSAX::writeAttValue(const char* value)
{
    if(xml_out.fail())// put a better check here!
        throw "SSSAX::writeAttValue NO file opened\n";
    xml_out << "\"" << value << "\"";
}

void       
SSSAX::writeStartTagClose()
{
    if(xml_out.fail())// put a better check here!
        throw "SSSAX::writeStartTagClose NO file opened\n";
    xml_out << ">\n";
}

void    
SSSAX::writeData(const char* data)
{
    if(xml_out.fail())// put a better check here!
        throw "SSSAX::writeData NO file opened\n";
    xml_out << data;
}

void    
SSSAX::writeEndTag(unsigned indent,const char* tag)
{
    if(xml_out.fail())// put a better check here!
        throw "SSSAX::writeEndTag NO file opened\n";
    for(unsigned i=0;i<indent;i++)
        xml_out << "\t";
    // write tag
    xml_out << "</" << tag << ">\n";
}

char* charPack(const char* a, const char* b)
{
    char *rt = new char[strlen(a)+strlen(b)+1];
    strcpy(rt,a);
    strcat(rt,b);
    return rt;
}

